﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using Storage.RemoteStorage.Sevice.Entity;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Specialized;
using System.Configuration;
using System.Security.Cryptography;
using System.Diagnostics;
using StorageManager.Interface;

namespace Storage.RemoteStorage.Client
{
    public delegate void ReportProgress(object Sender, ProgressStreamReportEventArgs e);

    public class WCFConnector
    {
        public readonly string LookuploadWord = ".{uploading}";
        public readonly string LookdownloadWord = ".{downloading}";
        public readonly string LookmoveWord = ".{moving}";
        #region Event
        public event ReportProgress ReportProgress;
        #endregion

        #region Statistics

        public long TotalByteDwonloadFolder { get; set; }
        public long TotalByteUploadFolder { get; set; }
        public long TotalByteDwonloadFile { get; set; }
        public long TotalByteUploadFile { get; set; }

        #endregion

        private string ServiceURL { get; set; }
        private RemoteStorageClient NavigationServices = null;
        private TransferServiceClient TransfertServices = null;
        public WCFConnector(string URLwcf)
        {
            this.ServiceURL = URLwcf;
            InitServiceNavigation();
            InitServiceDownload();
        }
        public WCFConnector()
        {
            NameValueCollection config = ConfigurationManager.GetSection("CloudStorage") as NameValueCollection;
            if (config != null)
            {
                this.ServiceURL = config["Storage.URL"];
            }
        }

        public bool isConnected
        {
            get
            {
                if (TransfertServices == null)
                    InitServiceDownload();
                if (NavigationServices == null)
                    InitServiceNavigation();
                if (TransfertServices.State == NavigationServices.State)
                    if (TransfertServices.State == CommunicationState.Created)
                        return true;
                    else
                        return false;
                else
                    return false;
            }
        }
        public void InitServiceDownload()
        {
            /*
      <netTcpBinding>
        <binding name="TransfertTCPEndPoint" receiveTimeout="10:01:00" sendTimeout="10:01:00"
          transferMode="Streamed" maxBufferPoolSize="66560" maxBufferSize="66560" maxConnections="66560"
          maxReceivedMessageSize="2147483647" portSharingEnabled="true">
          <readerQuotas maxDepth="32" maxStringContentLength="2147483647"
            maxArrayLength="2147483647" maxBytesPerRead="66560" />
        </binding>
      </netTcpBinding>
             */
            //NetTcpBinding transBinding = new NetTcpBinding();
            //transBinding.Name = "TransfertTCPEndPoint";
            //transBinding.ReceiveTimeout = TimeSpan.FromHours(10);
            //transBinding.SendTimeout = TimeSpan.FromHours(10);
            //transBinding.TransferMode = TransferMode.Streamed;
            //transBinding.MaxBufferPoolSize = 66560;
            //transBinding.MaxBufferSize = 66560;
            //transBinding.MaxConnections = 66560;
            //transBinding.MaxReceivedMessageSize = 2147483647;
            ////transBinding.PortSharingEnabled = true;
            //transBinding.ReaderQuotas.MaxDepth = 32;
            //transBinding.ReaderQuotas.MaxStringContentLength = 2147483647;
            //transBinding.ReaderQuotas.MaxArrayLength = 2147483647;
            //transBinding.ReaderQuotas.MaxBytesPerRead = 66560;

            //if (TransfertServices == null)//net.tcp://vmdev004dt.Storage-bpo.com:5000/RemoteStorage/TransferService.svc
            //    TransfertServices = new TransferServiceClient(transBinding, new EndpointAddress(new Uri(string.Format("net.tcp://{0}/RemoteStorage/TransferService.svc",this.ServiceURL ))));
            TransfertServices = new TransferServiceClient();
        }

        private void InitServiceNavigation()
        {
            //BasicHttpBinding navBinding = new BasicHttpBinding();
            //navBinding.Name = "BasicHttpBinding_IRemoteStorage";
            //navBinding.MaxReceivedMessageSize = 2147483647;
            //if (NavigationServices == null)//http://localhost:8001/RemoteStorage/RemoteStorage.svc
            //    NavigationServices = new RemoteStorageClient(navBinding, new EndpointAddress(new Uri(string.Format("http://{0}/RemoteStorage/RemoteStorage.svc", this.ServiceURL))));
            NavigationServices = new RemoteStorageClient();
        }

        #region Download
        public bool downloadFile(VirtualFile Remotefile, string ParentFolder)
        {
            if (Remotefile != null)
            {
                FileInfo localFile = new FileInfo(Path.Combine(ParentFolder, Remotefile.Name));
                if (localFile.Exists)
                    localFile.Delete();
                Stream Downloaded = null;
                string FileName = string.Empty;
                FileName = Path.Combine(Remotefile.Parent.RelativePath, Remotefile.Name);
                if (TransfertServices == null)
                    InitServiceDownload();
                long Filelengh = TransfertServices.DownloadFile(ref FileName, out Downloaded);
                using (FileStream fileStream = localFile.OpenWrite())
                {
                    //int bytesRead = -1;
                    int bufferSize = 64 * 1024;
                    byte[] bytes = new byte[bufferSize];
                    using (ProgressStream str = new ProgressStream(fileStream))
                    {
                        str.BytesWritten += new ProgressStreamReportDelegate(str_BytesWritten);
                        Downloaded.CopyTo(str, bufferSize);
                    }
                    return true;
                }
            }
            else
                throw new Exception("Fichier distant à la valeur Null");
        }

        void str_BytesWritten(object sender, ProgressStreamReportEventArgs args)
        {
            if (ReportProgress != null)
                ReportProgress(sender, args);
        }

        public bool DownloadFolder(VirtualFolder RemoteFolder, DirectoryInfo localDir, bool DeleteRemoteAfterCopy, bool eraseIfExist)
        {
            bool hasError = false;
            RemoteFolder = LookFolder(RemoteFolder.Name, RemoteFolder.Parent, LookOperation.Download);
            if (RemoteFolder == null)
                throw new Exception("Probleme de verouilage de dossier");
            DirectoryInfo destinationFolder = new DirectoryInfo(Path.Combine(localDir.FullName, RemoteFolder.Name));



            if (!destinationFolder.Exists)
            {
                destinationFolder.Create();
                destinationFolder.Refresh();
            }
            else
            {
                // TODO : 
            }

            if (RemoteFolder.nbFiles > 0)
            {
                bool End = false;
                int Index = 1;
                int PageSize = 30;
                do
                {

                    VirtualFile[] Files = NavigationServices.GetFilesPaged(out End, Index, PageSize, RemoteFolder);
                    foreach (VirtualFile item in Files)
                    {
                        FileInfo file = new FileInfo(Path.Combine(destinationFolder.FullName, item.Name));
                        if (file.Exists)
                        {
                            if (eraseIfExist)
                                file.Delete();
                            else
                                throw new Exception("Le fichier existe déja");
                        }
                        if (downloadFile(item, destinationFolder.FullName))
                        {
                            if (!CheckFile(file, RemoteFolder))
                                throw new Exception("il y a  une probleme dans la vérification du fichier teléchargé");
                        }
                        else
                        {
                            throw new Exception("il y a  une probleme dans le teléchargement du fichier");
                        }

                    }
                    Index++;

                } while (!End);
            }
            if (RemoteFolder.nbSubFolder > 0)
            {
                bool End = false;
                int Index = 1;
                int PageSize = 30;
                do
                {

                    VirtualFolder[] subFolders = NavigationServices.GetFolderPaged(out End, Index, PageSize, RemoteFolder);
                    foreach (VirtualFolder item in subFolders)
                    {
                        if (!DownloadFolder(item, destinationFolder, DeleteRemoteAfterCopy, eraseIfExist))
                            throw new Exception("Probleme de telechergement de dossier " + item.RelativePath);
                    }
                    Index++;

                } while (!End);
            }
            UnLookLocalFolder(destinationFolder, LookOperation.Download);
            RemoteFolder = UnLookFolder(RemoteFolder.Name, RemoteFolder.Parent, LookOperation.Download);

            return true;
        }

        public Task DownloadFolderFromRemoteTask(VirtualFolder RemoteFolder, DirectoryInfo localDir, bool DeleteRemoteAfterCopy, bool eraseIfExist)
        {
            return new Task(() =>
            {
                try
                {
                    this.DownloadFolder(RemoteFolder, localDir, DeleteRemoteAfterCopy, eraseIfExist);
                }
                catch (Exception ex)
                {
                    LogManagerEngine.logger.Error<Exception>(ex);
                }

            });
        }

        #endregion

        #region Upload
        public bool UploadFile(VirtualFolder remoteFolder, FileInfo localFile)
        {
            if (remoteFolder != null)
            {
                if (TransfertServices == null)
                    InitServiceDownload();
                LogManagerEngine.logger.Info("Demarrage Upload Fichier {0} vers {1} à {2}", localFile.FullName, remoteFolder.RelativePath, DateTime.Now.ToString("G"));
                Stopwatch watch = Stopwatch.StartNew();
                using (ProgressStream str = new ProgressStream(localFile.OpenRead()))
                {
                    str.BytesRead += new ProgressStreamReportDelegate(str_BytesRead);
                    TransfertServices.UploadFile(
                    Path.Combine(remoteFolder.RelativePath, localFile.Name),
                    localFile.Length, str);
                    watch.Stop();
                    LogManagerEngine.logger.Info("Fin Copie Dossier {0} vers {1} à {2}; Durée : {3}", localFile.FullName, remoteFolder.RelativePath, DateTime.Now.ToString("G"), watch.Elapsed.ToString("G"));
                    return true;
                }
            }
            else
                throw new Exception("Dossier distant à la valeur Null");
        }

        public bool UploadFolder(VirtualFolder RemoteFolder, DirectoryInfo localDir, bool DeleteLocalAfterCopy)
        {
            LogManagerEngine.logger.Info("Demarrage Copie Dossier {0} vers {1} à {2}", localDir.FullName, RemoteFolder.RelativePath, DateTime.Now.ToString("G"));
            Stopwatch watch = Stopwatch.StartNew();
            VirtualFolder WorkingFolder = null;
            if (NavigationServices == null)
                InitServiceNavigation();
            if (TransfertServices == null)
                InitServiceDownload();
            if (!RemoteFolder.IsLoaded)
                RemoteFolder = NavigationServices.GetFolder(RemoteFolder.Name, RemoteFolder.Parent);
            WorkingFolder = NavigationServices.GetFolder(localDir.Name, RemoteFolder);
            localDir = LookLocalFolder(localDir, LookOperation.Upload);
            if (WorkingFolder == null)
                WorkingFolder = NavigationServices.CreateFolder(localDir.Name, RemoteFolder);
            else
                WorkingFolder = LookFolder(WorkingFolder.Name, WorkingFolder.Parent, LookOperation.Upload);
            //WorkingFolder = LookFolder(WorkingFolder.Name, WorkingFolder.Parent, LookOperation.Upload);
            if (WorkingFolder != null)
            {
                FileInfo[] Files = localDir.GetFiles();
                if (Files != null)
                {
                    #region Upload File to Remote Folder
                    //Create Remote Folder to Upload File
                    LogManagerEngine.logger.Info("Nombre de Fichier à uploder {0}", Files.Count());
                    foreach (FileInfo file in Files)
                    {
                        UploadFile(WorkingFolder, file);
                        LogManagerEngine.logger.Info("vérification Fichier uploder : {0}", file.Name);
                        if (CheckFile(file, WorkingFolder))
                        {
                            if (DeleteLocalAfterCopy)
                            {
                                file.Delete();
                                LogManagerEngine.logger.Info("suppression Fichier uploder : {0}", file.Name);
                            }
                        }
                        else
                        {
                            LogManagerEngine.logger.Info("Echec Fichier uploder (Probleme CRC): {0}", file.Name);
                            throw new Exception("probleme erreur crc sur fichiers uploadés");
                        }
                    }
                    #endregion
                }
                localDir.Refresh();
                DirectoryInfo[] subDir = localDir.GetDirectories();
                if (subDir != null)
                {
                    int subdirCount = subDir.Count();
                    if (subdirCount > 0)
                    {
                        LogManagerEngine.logger.Info("Nombre de sous-dossier à uploder {0}", subdirCount);
                        foreach (DirectoryInfo item in subDir)
                        {
                            UploadFolder(WorkingFolder, item, DeleteLocalAfterCopy);
                        }
                    }
                }
                if (DeleteLocalAfterCopy)
                {
                    localDir = UnLookLocalFolder(localDir, LookOperation.Upload);
                    localDir.Delete(true);
                    LogManagerEngine.logger.Info("suppression dossier aprés uplod {0}", localDir.Name);
                }
                else
                    localDir = UnLookLocalFolder(localDir, LookOperation.Upload);
                WorkingFolder = UnLookFolder(WorkingFolder.Name, WorkingFolder.Parent, LookOperation.Upload);
            }
            watch.Stop();
            LogManagerEngine.logger.Info("Fin Copie Dossier {0} vers {1} à {2}; Durée : {3}", localDir.FullName, RemoteFolder.RelativePath, DateTime.Now.ToString("G"), watch.Elapsed.ToString("G"));
            return true;
        }

        private VirtualFolder LookFolder(string Name, VirtualFolder Parent, LookOperation typelook)
        {
            VirtualFolder Folder = NavigationServices.GetFolder(Name, Parent);
            if (Folder == null)
                throw new System.IO.DirectoryNotFoundException("Dossier distant introuvable");
            string renamedFolder = string.Empty;
            switch (typelook)
            {
                case LookOperation.Upload:
                    renamedFolder = string.Format("{0}{1}", Name, LookuploadWord);
                    break;
                case LookOperation.Download:
                    renamedFolder = string.Format("{0}{1}", Name, LookdownloadWord);
                    break;
                case LookOperation.Move:
                    renamedFolder = string.Format("{0}{1}", Name, LookmoveWord);
                    break;
                default:
                    renamedFolder = Name;
                    break;
            }


            if (!NavigationServices.RenameFolder(Name, renamedFolder, Parent))
                throw new Exception("Probleme de verouiialge de dossier");
            else
            {
                Folder = NavigationServices.GetFolder(renamedFolder, Parent);
                if (Folder == null)
                    throw new System.IO.DirectoryNotFoundException("Impossible de charger le dossier renommer");
                else
                    return Folder;
            }
        }

        private VirtualFolder UnLookFolder(string Name, VirtualFolder Parent, LookOperation typelook)
        {
            string strlook = string.Empty;
            switch (typelook)
            {
                case LookOperation.Upload:
                    strlook = LookuploadWord;
                    break;
                case LookOperation.Download:
                    strlook = LookdownloadWord;
                    break;
                case LookOperation.Move:
                    strlook = LookmoveWord;
                    break;
                default:
                    strlook = Name;
                    break;
            }
            VirtualFolder Folder = NavigationServices.GetFolder(Name, Parent);
            if (Folder == null)
                throw new System.IO.DirectoryNotFoundException("Dossier distant introuvable");
            if (!Name.EndsWith(strlook))
                throw new Exception("Ce dossier n'est pas verouillé");

            string renamedFolder = Name.Substring(0, Name.Length - strlook.Length);
            if (!NavigationServices.RenameFolder(Name, renamedFolder, Parent))
                throw new Exception("Probleme de dévérouialge de dossier");
            else
            {
                Folder = NavigationServices.GetFolder(renamedFolder, Parent);
                if (Folder == null)
                    throw new System.IO.DirectoryNotFoundException("Impossible de charger le dossier renommer");
                else
                    return Folder;
            }
        }

        private DirectoryInfo UnLookLocalFolder(DirectoryInfo localFolder, LookOperation typelook)
        {
            string strlook = string.Empty;
            switch (typelook)
            {
                case LookOperation.Upload:
                    strlook = LookuploadWord;
                    break;
                case LookOperation.Download:
                    strlook = LookdownloadWord;
                    break;
                case LookOperation.Move:
                    strlook = LookmoveWord;
                    break;
                default:
                    strlook = "";
                    break;
            }
            if (!localFolder.Exists)
                throw new System.IO.DirectoryNotFoundException("Dossier local introuvable");
            if (!localFolder.Name.EndsWith(strlook))
                throw new Exception("Ce dossier n'est pas verouillé");

            string renamedFolder = localFolder.Name.Substring(0, localFolder.Name.Length - strlook.Length);
            try
            {
                if (!Directory.Exists(Path.Combine(localFolder.Parent.FullName, renamedFolder)))
                    localFolder.MoveTo(Path.Combine(localFolder.Parent.FullName, renamedFolder));
                else
                {
                    //Move File to existing Folder to existing Folder
                    foreach (FileInfo files in localFolder.EnumerateFiles())
                    {
                        files.CopyTo(Path.Combine(localFolder.Parent.FullName, renamedFolder, files.Name), true);
                    }
                    //delete Old Folder
                    if (localFolder.Exists)
                        localFolder.Delete(true);
                }
            }
            catch
            {
                throw new Exception("Probleme de dévérouialge de dossier");
            }
            localFolder = new DirectoryInfo(Path.Combine(localFolder.Parent.FullName, renamedFolder));
            if (!localFolder.Exists)
                throw new System.IO.DirectoryNotFoundException("Impossible de charger le dossier unlocker");
            else
                return localFolder;
        }

        private DirectoryInfo LookLocalFolder(DirectoryInfo localFolder, LookOperation typelook)
        {
            if (!localFolder.Exists)
                throw new System.IO.DirectoryNotFoundException("Dossier local introuvable");
            string renamedFolder = string.Empty;
            switch (typelook)
            {
                case LookOperation.Upload:
                    renamedFolder = string.Format("{0}{1}", localFolder.Name, LookuploadWord);
                    break;
                case LookOperation.Download:
                    renamedFolder = string.Format("{0}{1}", localFolder.Name, LookdownloadWord);
                    break;
                case LookOperation.Move:
                    renamedFolder = string.Format("{0}{1}", localFolder.Name, LookmoveWord);
                    break;
                default:
                    renamedFolder = localFolder.Name;
                    break;
            }

            try
            {
                localFolder.MoveTo(Path.Combine(localFolder.Parent.FullName, renamedFolder));
                localFolder.Refresh();
            }
            catch (Exception ex)
            {
                throw new Exception("Probleme de verouiialge de dossier", ex);
            }
            return localFolder;
        }

        public Task UploadFolderFromLocalTask(VirtualFolder RemoteDestinationFolder, System.IO.DirectoryInfo LocalFolder, bool DeleteLocalAfterCopy)
        {
            return new Task(() =>
            {
                try
                {
                    this.UploadFolder(RemoteDestinationFolder, LocalFolder, DeleteLocalAfterCopy);
                }
                catch (Exception ex)
                {
                    LogManagerEngine.logger.Error<Exception>(ex);
                }

            });
        }

        void str_BytesRead(object sender, ProgressStreamReportEventArgs args)
        {
            if (ReportProgress != null)
                ReportProgress(sender, args);
        }
        #endregion

        #region Navigation/Modification Folder
        public VirtualFolder GetRemoteFolder(string Name, VirtualFolder Parent)
        {
            if (NavigationServices == null)
                InitServiceNavigation();
            return NavigationServices.GetFolder(Name, Parent);
        }

        public VirtualFolder[] GetRemoteFolderPaged(VirtualFolder Parent, int Index, int PageSize, out bool End)
        {
            if (NavigationServices == null)
                InitServiceNavigation();
            return NavigationServices.GetFolderPaged(out End, Index, PageSize, Parent);
        }

        public VirtualFile[] GetRemoteFilePaged(VirtualFolder Parent, int Index, int PageSize, out bool End)
        {
            if (NavigationServices == null)
                InitServiceNavigation();
            return NavigationServices.GetFilesPaged(out End, Index, PageSize, Parent);
        }

        public VirtualFolder GetRemoteFolderbyIndex(int index, VirtualFolder Parent)
        {
            if (NavigationServices == null)
                InitServiceNavigation();
            return NavigationServices.GetFolderbyIndex(index, Parent);
        }

        public VirtualFolder GetRemoteFolderbyPath(string pathFolder, VirtualFolder Parent, bool CreatIfNoExist)
        {
            if (NavigationServices == null)
                InitServiceNavigation();
            return NavigationServices.GetFolderbyPath(pathFolder, Parent, CreatIfNoExist);
        }

        public VirtualFolder GetRootFolder()
        {
            if (NavigationServices == null)
                InitServiceNavigation();
            return NavigationServices.GetRootFolder();
        }

        public VirtualFolder CreateFolder(string Name, VirtualFolder Parent)
        {
            if (NavigationServices == null)
                InitServiceNavigation();
            return NavigationServices.CreateFolder(Name, Parent);
        }
        public bool DeleteFolder(string Name, VirtualFolder Parent)
        {
            if (NavigationServices == null)
                InitServiceNavigation();
            return NavigationServices.DeleteFolder(Name, Parent);
        }
        public bool MoveRemoteFolder(string Name, VirtualFolder oldParent, VirtualFolder newParent)
        {
            if (NavigationServices == null)
                InitServiceNavigation();
            return NavigationServices.MoveFolder(Name, oldParent, newParent);
        }
        #endregion

        #region Manipulation Remote File
        public VirtualFile GetRemoteFile(string Name, VirtualFolder Parent)
        {
            if (NavigationServices == null)
                InitServiceNavigation();
            return NavigationServices.GetFile(Name, Parent);
        }

        public bool DeleteRemoteFile(string Name, VirtualFolder Parent)
        {
            if (NavigationServices == null)
                InitServiceNavigation();
            return NavigationServices.DeleteFile(Name, Parent);
        }
        #endregion

        #region File Verification
        public bool CheckFile(FileInfo localFile, VirtualFolder RemoteFolder)
        {
            if (NavigationServices == null)
                InitServiceNavigation();
            VirtualFile remoteFile = NavigationServices.GetFile(localFile.Name, RemoteFolder);
            string HashRemoteFile = NavigationServices.GetHashCode(remoteFile, RemoteFolder);
            string HashLocalFile = string.Empty;
            using (var md5 = MD5.Create())
            {
                using (var stream = File.OpenRead(localFile.FullName))
                {
                    HashLocalFile = Convert.ToBase64String(md5.ComputeHash(stream));
                }
            }
            if (HashRemoteFile == string.Empty)
                throw new Exception("Fichier distant introuvable");
            if (HashLocalFile == string.Empty)
                throw new Exception("Fichier Local introuvable");
            return HashLocalFile.Equals(HashRemoteFile);
        }
        #endregion
    }

    public enum LookOperation
    {
        Upload = 0,
        Download = 1,
        Move = 2
    }
}